题解:P16487 [GKS 2014 #C] Taking Metro

838 字
4 分钟
题解:P16487 [GKS 2014 #C] Taking Metro

题面传送门:P16487 [GKS 2014 #C] Taking Metro

题目大意#

给定两个地铁站,求它们之间的最短通勤时间。

思路讲解#

从题面中不难看出,这本质上是道求最短路的题,而这道题的难点在于建图。

我们可以把时间当作边权,这道题对时间的描述就分别有:

  1. 车站间的运行时间
  2. 换乘通道的步行时间
  3. 预期的等待时间

对于前两种描述,我们直接把两个站点连起来,以时间为边权,建边就完成了。
第三种描述看起来类似点权,我们熟知的最短路算法并不能简单地处理点权。因此可以考虑把预期等待时间也转成边来处理。

转化具体思路

常识中,我们要在地铁 ii 号线的站台上等候 wiw_i 分钟,然后登上地铁列车。这里的 wiw_i 分钟可以说你一直在同一个点。
现在把它改成:地铁一直在那里,且可以一直等着我们,但我们走得很慢,需要 wiw_i 分钟才能走上车。这里的 wiw_i 分钟,你从站台走上了车,相当于走出了一条边。

综上可以将一个地铁车站分为两部分(站台和车上),对这两部分连边即可。

处理完跑最短路即可。

代码实现#

最短路算法的选择

先计算一下这个图的最大点数 nn 和最大边数 mm:

每个测试用例中的车站总数不超过 10001000,一站分为两部分,所以 n=2000n=2000。

每个站有两部分,每站各部分之间有无向边,即 20002000 条有向边;外加每组相邻站有无向边,即最多 9992999^2 条边,所以 mm 约为 10610^6 。

题目还是多测,1≤T≤1001 \le T \le 100,别说 Floyd 了,直接跑 nn 遍甚至 n/2n/2 遍(不带车上点)加堆优化的 Dijkstra 也过不了。但题目中 1≤Q≤101 \le Q \le 10 的范围可以让我们在每次询问的时候跑 Dijkstra 算法,这样就可以通过了。

存储

存储可用链式前向星和邻接表。线路、是否上车、站点号的处理若要通过所有数据,则可以通过创建映射表的方法来解决。主要思想就是赋予每个站一个编号,特别的,不在车上的编号为站点编号 +1000+1000,以达到一分区一编号的效果。

for(int i=1;i<=n;i++){
cin>>sn[i]>>w[i];
for(int j=1;j<=sn[i];j++){
num++;
mp[make_pair(i,j)]=num;
if(j!=sn[i]){
cin>>ti;
add(mp[make_pair(i,j)],mp[make_pair(i,j)]+1,ti);
add(mp[make_pair(i,j)]+1,mp[make_pair(i,j)],ti);
//两站站内之间建双向边
}
add(mp[make_pair(i,j)],mp[make_pair(i,j)]+1000,0);
add(mp[make_pair(i,j)]+1000,mp[make_pair(i,j)],w[i]);
//当前站站内外建双向边
}
}
完整代码
#include<bits/stdc++.h>
using namespace std;
int t,n,sn[105],w[105],ti,m,m1,s1,m2,s2,tr,qu,x1,yl,x2,y2;
int cnt,to[4005],val[4005],nxt[4005],h[2005];
int num,dis[2005],uu,vv,num1,num2;
bool b[2005];
map<pair<int,int>,int>mp;
priority_queue<pair<int,int>,vector<pair<int,int> >,greater<pair<int,int> > >q;
void add(int a,int b,int c){
cnt++;
to[cnt]=b;
val[cnt]=c;
nxt[cnt]=h[a];
h[a]=cnt;
}
int main(){
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
cin>>t;
for(int z=1;z<=t;z++){
cin>>n;
memset(h,0,sizeof(h));//不清空会导致66行死循环
num=0;cnt=0;//多测不清空,爆零两行泪
for(int i=1;i<=n;i++){
cin>>sn[i]>>w[i];
for(int j=1;j<=sn[i];j++){
num++;
mp[make_pair(i,j)]=num;
if(j!=sn[i]){
cin>>ti;
add(mp[make_pair(i,j)],mp[make_pair(i,j)]+1,ti);
add(mp[make_pair(i,j)]+1,mp[make_pair(i,j)],ti);
//两站站内之间建双向边
}
add(mp[make_pair(i,j)],mp[make_pair(i,j)]+1000,0);
add(mp[make_pair(i,j)]+1000,mp[make_pair(i,j)],w[i]);
//当前站站内外建双向边
}
}
cin>>m;
for(int i=1;i<=m;i++){
cin>>m1>>s1>>m2>>s2>>tr;
add(mp[make_pair(m1,s1)]+1000,mp[make_pair(m2,s2)]+1000,tr);
add(mp[make_pair(m2,s2)]+1000,mp[make_pair(m1,s1)]+1000,tr);
//站外换乘通道建双向边
}
//补药在此处预处理最短路!会TLE
cin>>qu;
cout<<"Case #"<<z<<":\n";
for(int k=1;k<=qu;k++){
cin>>x1>>yl>>x2>>y2;
num1=mp[make_pair(x1,yl)]+1000;num2=mp[make_pair(x2,y2)]+1000;
for(int i=1;i<=1000+num;i++){
dis[i]=0x3fffffff;
b[i]=0;
}
dis[num1]=0;
q.push(make_pair(0,num1));
while(!q.empty()){
uu=q.top().second;
q.pop();
if(b[uu]==1) continue;
b[uu]=1;
for(int j=h[uu];j!=0;j=nxt[j]){//这里是66行
vv=to[j];
if(dis[vv]>dis[uu]+val[j]){
dis[vv]=dis[uu]+val[j];
q.push(make_pair(dis[vv],vv));
}
}
}
cout<<' ';
if(dis[num2]==0x3fffffff){
cout<<"-1\n";
}else{
cout<<dis[num2]<<'\n';
}
}
}
return 0;
}

文章分享

如果这篇文章对你有帮助,欢迎分享给更多人!

题解:P16487 [GKS 2014 #C] Taking Metro
https://zhedaotixuanbo.pages.dev/posts/题解:P16487 [GKS 2014 C] Taking Metro/
作者
zhedaotixuanbo
发布于
2026-08-28
许可协议
CC BY-NC-SA 4.0
Profile Image of the Author
zhedaotixuanbo
这道题选什么? _____!
公告
分类
标签
站点统计
文章
17
分类
1
标签
21
总字数
6,295
运行时长
0 天
最后活动
0 天前
站点信息
构建平台
Cloudflare Pages
博客版本
ZTXB v1.0.0
文章许可
CC BY-NC-SA 4.0